/**
 * @author [sunshinebooming]
 * @email [sunshinebooming@gmail.com]
 * @create date 2020-11-14 00:03:19
 * @modify date 2020-11-14 00:03:19
 * @desc [linux platform netif adapter]
 */
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>

#include <pcap.h>

#include "app_api.h"
#include "lwip/timers.h"
#include "config.h"
#include "tcpip_adapter.h"

/* ======================================== Private Parameters ======================================== */
#define IFNAME0 'e'
#define IFNAME1 'n'
#define PCAP_CAPTURE_MAX 2048
#define DEV_NAME_MAX 16
static char *linux_pcap_name = NULL;
static pcap_t *linux_pcap = NULL;
static struct netif linux_netif = { 0 };

/* ======================================== Private Functions ======================================== */
static err_t linux_firewall_disable(struct netif *netif);
static err_t linux_firewall_enable(struct netif *netif);
static err_t linux_lwip_init(struct netif *netif);
static err_t linux_pcap_lookup(struct netif *netif);
static err_t linux_pcap_open(struct netif *netif);
static err_t linux_link_output(struct netif *netif, struct pbuf *pfirst);
static void *linux_netif_packet_capture(void *arg);

static err_t linux_firewall_disable(struct netif *netif)
{
	char cmd[256] = { 0 };

	printf("disable firewall\n");

	/* disable firewall */
	printf("sudo ufw disable\n");
	system("sudo ufw disable");

#if SIM_TCP_ECHO_SERVER
	memset(cmd, 0, 256);
	sprintf(cmd, "sudo iptables -A INPUT -p tcp --destination-port %u -j DROP", TCP_ECHO_SERVER_PORT);
	printf("%s\n", cmd);
	system(cmd);
#endif /* SIM_TDP_ECHO_SERVER */

#if SIM_UDP_ECHO_SERVER
	memset(cmd, 0, 256);
	sprintf(cmd, "sudo iptables -A INPUT -p tcp --destination-port %u -j DROP", UDP_ECHO_SERVER_PORT);
	printf("%s\n", cmd);
	system(cmd);
#endif /* SIM_UDP_ECHO_SERVER */

	return ERR_OK;
}

static err_t linux_firewall_enable(struct netif *netif)
{
	char cmd[256] = { 0 };

	/* enable firewall */
	// printf("sudo ufw enable\n");
	// system("sudo ufw enable");

#if SIM_TCP_ECHO_SERVER
	memset(cmd, 0, 256);
	sprintf(cmd, "sudo iptables -A INPUT -p tcp --destination-port %u -j ACCEPT", TCP_ECHO_SERVER_PORT);
	printf("%s\n", cmd);
	system(cmd);
#endif /* SIM_TCP_ECHO_SERVER */

#if SIM_UDP_ECHO_SERVER
	memset(cmd, 0, 256);
	sprintf(cmd, "sudo iptables -A INPUT -p tcp --destination-port %u -j ACCEPT", UDP_ECHO_SERVER_PORT);
	printf("%s\n", cmd);
	system(cmd);
#endif /* SIM_UDP_ECHO_SERVER */

	return ERR_OK;
}

static err_t linux_link_output(struct netif *netif, struct pbuf *pfirst)
{
	register u32_t *src;
	register u8_t *dest;
	register u32_t size;

	/* Current pbuf when combining pbufs. */
	struct pbuf *pcur;
	/* New pbuf as destination of combined pbufs. */
	struct pbuf *pnew;
	/* Total length of new pbuf payload. */
	u32_t tot_len;

	/* To support pbuf chains when sending the loop is used to add
	 * multiple pbuf payloads into the transmit buffer. When the packet has
	 * been formed then it can be transmitted. The tot_len and len members
	 * of the pbuf structure determine whether a pbuf is a partial packet.
	 */
	do {
		/* Each packet has 2 free bytes at the start of the first
		 * pbuf in the chain. This is primarily to align the payload
		 * to a 32 bit boundary. It works well for us as we can have
		 * an aligned buffer to stream into the ethernet transmit
		 * buffer directly.
		 * The payload of the first pbuf has a word free (ARCH_HW_HLEN)
		 * where the packet length word is written.
		 */

		/* Stream out the payloads from each pbuf in the chain.
		 * Start at the first.
		 */
		pcur = pfirst;

		/* Holder for an aligned pbuf. If the length of the payload of a
		 * pbuf is not a multiple of 4 bytes then it cannot be streamed into
		 * the transmit buffer as the buffer requires 32-bit accesses.
		 * Therefore anything other than aligned writes will result in gaps
		 * in the data between pbuf payloads! */
		pnew = NULL;

		/* Iterate trough the pbuf chain. */
		while (1) {
			/* Setup registers for stream instruction.
			 * Source data is the start of the pbuf payload. */
			src = (u32_t *)pcur->payload;
			/* Size is the length of this pbuf'f payload. */
			size = pcur->len;

			printf("linux_link_output: pbuf chain size %" U32_F " %p\n", size, src);

			/* If the length of pbuf is not a multiple of 4 it cannot be streamed.
			 * The ETH_DATA register will only receive 32-bit writes correctly.
			 */
			if ((size & 3) != 0) {
				/* If, however, this is the last pbuf in a packet then we can
				 * send pad bytes after to ensure a 32-bit write at the end. The
				 * packet size in the frame header ensures the hardware knows
				 * when to stop transmitting.
				 */
				if (pcur->next != NULL) {
					printf("linux_link_output: Alignment... copying remaining chain\n");

					/* Combine this and remaining pbufs into one buffer.
					 * Do not use the pbuf_coalesce function as this will
					 * free the original pbufs and cause issues.
					 * The total_len for the packet is used to create a new
					 * pbuf large enough for all these other pbufs.
					 */
					pnew = pbuf_alloc(PBUF_RAW, pcur->tot_len, PBUF_RAM);
					if (pnew == NULL) {
						/* Out of memory for creating a new pbuf. */
						/* The original packet will still be sent.
						 * It will be picked up by a network analyser and
						 * reported as malformed.
						 */
						printf("linux_link_output: copy failed\n");
					}
					else {
						/* Copy this packet and all subsequent packets into
						 * the new pbuf. Technically we could stop if any of
						 * these pbufs ends on a 32-bit boundary but this
						 * would require additional complexity without a
						 * massive increase in speed.
						 */
						dest = pnew->payload;
						tot_len = 0;

						do {
							src = (u32_t *)pcur->payload;
							size = pcur->len;
							memcpy((u8_t *)dest, (u8_t *)src, size);

							/* Adjust the new pbuf with the sizes of the original
							 * pbufs. The original pbufs are not modified or freed
							 * as the caller will take care of that.
							 */
							tot_len += size;
							dest += size;
							/* Move to the next pbuf in the chain of original pbufs. */
							pcur = pcur->next;
						} while (pcur);

						/* Finalise the important bits of the new pbuf. */
						src = (u32_t *)pnew->payload;
						size = tot_len;
						size = ((size + 3) & (~3));

						printf("linux_link_output: new pbuf size now %" U32_F "\n", size);
					}
				}
			}

			pcap_sendpacket(linux_pcap, (u8_t *)src, size);

			/* If we have created a new aligned buffer then free it now.
			 * Remember we are leaving the original pbuf chain intact.
			 */
			if (pnew != NULL) {
				pbuf_free(pnew);
				break;
			}

			/* Are we at the end of the pbuf chain? Test for sending the
			 * total_len number of bytes.
			 */
			if (pcur->len == pcur->tot_len) {
				break;
			}

			/* Hard end of chain detected - catch this case. */
			if (pcur->next == NULL) {
				break;
			}

			/* Move to next pbuf in chain. */
			pcur = pcur->next;
		}

		// Move to next packet in chain. There probably isn't one.
		pfirst = pcur->next;
	} while (pfirst != NULL);
	return 0;
}

static err_t linux_pcap_lookup(struct netif *netif)
{
	printf("linux pcap lookup\n");

	char err_buf[PCAP_ERRBUF_SIZE];
	linux_pcap_name = pcap_lookupdev(err_buf);
	if(NULL == linux_pcap_name) {
		printf("linux pcap lookup error: %s\n", err_buf);
		return ERR_IF;
	}

	printf("linux pcap dev found: %s\n", linux_pcap_name);
	return ERR_OK;
}

static err_t linux_pcap_open(struct netif *netif)
{
	printf("linux pcap open\n");
	char err_buf[PCAP_ERRBUF_SIZE];

	/* open pcap dev for capture packets */
	linux_pcap = pcap_open_live(linux_pcap_name, PCAP_CAPTURE_MAX, 1, -1, err_buf);
	if(NULL == linux_pcap) {
		printf("pcap open error: %s\n", err_buf);
		return ERR_IF;
	}
	printf("opened pcap dev: %s\n", linux_pcap_name);

	return ERR_OK;
}

static err_t linux_lwip_init(struct netif *netif)
{
	printf("linux lwip init\n");
	/* setup netif name */
	netif->name[0] = IFNAME0;
	netif->name[1] = IFNAME1;

	/* regist IP layer packet output interface */
	netif->output = etharp_output;
	/* regist ethernet layer packet output interface */
	netif->linkoutput = linux_link_output;

	/* setup mac address */
	/* 08:00:12:34:56:78 */
	netif->hwaddr[0] = HOST_MAC_0;
	netif->hwaddr[1] = HOST_MAC_1;
	netif->hwaddr[2] = HOST_MAC_2;
	netif->hwaddr[3] = HOST_MAC_3;
	netif->hwaddr[4] = HOST_MAC_4;
	netif->hwaddr[5] = HOST_MAC_5;
	netif->hwaddr_len = ETHARP_HWADDR_LEN;
	printf("MAC : %02x:%02x:%02x:%02x:%02x:%02x\n", netif->hwaddr[0], \
			netif->hwaddr[1], netif->hwaddr[2], netif->hwaddr[3], \
			netif->hwaddr[4], netif->hwaddr[5]);

	/* setup MTU */
	netif->mtu = 1500;
	netif->num = 0;
	netif->next = NULL;

	netif->flags = NETIF_FLAG_BROADCAST | NETIF_FLAG_ETHARP | NETIF_FLAG_ETHERNET | NETIF_FLAG_IGMP;

	return ERR_OK;
}

static void *linux_netif_packet_capture(void *arg)
{
	int ret;
	struct pcap_pkthdr *header;
	const u_char *pkt_data;
	struct pbuf *pnew;

	printf("packet capture: %s\n", linux_pcap_name);

	/* Read the packets */
	while ((ret = pcap_next_ex(linux_pcap, &header, &pkt_data)) >= 0) {

#if NO_SYS
		/* Check lwip timeout */
		sys_check_timeouts();
#endif

		if (ret == 0) {
			/* Timeout elapsed */
			continue;
		}
		pnew = pbuf_alloc(PBUF_RAW, header->len, PBUF_RAM);
		if (pnew != NULL) {
			memcpy(pnew->payload, pkt_data, header->len);
			/* Forward packets recved on linux, into lwip core */
			linux_netif.input(pnew, &linux_netif);
		}
	}
	return NULL;
}

/* ======================================== Public Functions ======================================== */

/* Return current time in milliseconds */
u32_t sys_now(void)
{
	static long long prev_time_in_mill = 0;
	long long time_in_mill;
	struct timeval tv;

	/* Get current time on linux */
	gettimeofday(&tv, NULL);
	time_in_mill = (long long)(tv.tv_sec) * 1000 + (long long)((tv.tv_usec) / 1000);
	if (prev_time_in_mill == 0) {
		prev_time_in_mill = time_in_mill;
	}

	/* Get time elapsed since lwip init */
	u32_t mill = (u32_t)(time_in_mill - prev_time_in_mill);
	return mill;
}

#if LWIP_IGMP
err_t linux_igmp_mac_filter(struct netif *netif, const ip4_addr_t *group, u8_t action)
{
	return ERR_OK;
}
#endif

err_t linux_netif_init(struct netif **netif)
{
	printf("linux netif init\r\n");

	/* 1) disable firewall */
	linux_firewall_disable(&linux_netif);

	/* 2) lookup linux device & get its name */
	linux_pcap_lookup(&linux_netif);

	/* 2) setup ip address */
	ip_addr_t ip;
	ip_addr_t gw;
	ip_addr_t mask;

#if SIM_DHCP_ENABLE
	IP4_ADDR(&ip, 0, 0, 0, 0);
	IP4_ADDR(&gw, 0, 0, 0, 0);
	IP4_ADDR(&mask, 0, 0, 0, 0);
#else /* !SIM_DHCP_ENABLE */
	HOST_IP_ADDR(ip);
	HOST_GW_ADDR(gw);
	HOST_MASK_ADDR(mask);
#endif /* SIM_DHCP_ENABLE */

	printf("IP  : %u.%u.%u.%u\n", ip.addr & 0xff, (ip.addr >> 8) & 0xff, \
			(ip.addr >> 16) & 0xff, (ip.addr >> 24) & 0xff);
	printf("GW  : %u.%u.%u.%u\n", gw.addr & 0xff, (gw.addr >> 8) & 0xff, \
			(gw.addr >> 16) & 0xff, (gw.addr >> 24) & 0xff);
	printf("MASK: %u.%u.%u.%u\n", mask.addr & 0xff, (mask.addr >> 8) & 0xff, \
			(mask.addr >> 16) & 0xff, (mask.addr >> 24) & 0xff);

	/* 3) init lwip core */
	lwip_init();

	/* 4) add current netif */
	if(NULL == netif_add(&linux_netif, &ip, &mask, &gw, NULL, linux_lwip_init, ethernet_input)) {
		printf("netif add error\n");
		return ERR_IF;
	}

	/* 5) setup default netif */
	netif_set_default(&linux_netif);
	netif_set_link_down(&linux_netif);
	netif_set_down(&linux_netif);
	netif_set_up(&linux_netif);

	/* 6) setup linux pcap device */
	linux_pcap_open(&linux_netif);
	netif_set_link_up(&linux_netif);

#if SIM_DHCP_ENABLE
	if(ERR_OK == dhcp_start(&linux_netif)) {
		// dhcp_mutex_get();
		printf("dhcp start success\n");
	}
	else {
		printf("dhcp start error\n");
	}
#endif /* SIM_DHCP_ENABLE */

	*netif = &linux_netif;

	return ERR_OK;
}

void linux_netif_deinit(void)
{
	linux_firewall_enable(&linux_netif);
}

err_t create_packet_capture_thread(pthread_t *thread)
{
	pthread_t task_thread;
	printf("create packet capture thread\n");

	int ret = pthread_create(&task_thread, NULL, linux_netif_packet_capture, NULL);
	if (ret) {
		printf("create packet capture thread error\n");
		return ERR_IF;
	}

	*thread = task_thread;
	printf("create packet capture thread success\n");
	return ERR_OK;
}

